﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Routing;

using Lilium.Linq;
using Lilium.LExpress;

namespace Lilium.Web.Mvc.Metadata
{
    /// <summary>
    /// Default implementation of the metadata service.
    /// </summary>
    public sealed class MetadataService : IMetadataService
    {
        /// <summary>
        /// Creates an instance of the MetadataService.
        /// </summary>
        /// <param name="serviceProvider">
        /// Service provider for the MetadataService.
        /// </param>
        public MetadataService(IServiceProvider serviceProvider)
        {
            if (serviceProvider == null) throw new ArgumentNullException("serviceProvider");

            m_ServiceProvider = serviceProvider;
        }

        #region IMetadataService Members

        public RouteValueDictionary GetActionRouteValues<TController>(Expression<Func<TController, ActionResult>> action)
        {
            //TODO: Заменить PartialEvaluator.Eval на получение параметров через рефлексию.
            if (action == null) throw new ArgumentNullException("action");

            var methodCall = (MethodCallExpression)action.Body;

            var controllerType = methodCall.Method.DeclaringType;
            var controllerTypeName = controllerType.Name;

            var routeValues = new RouteValueDictionary();

            routeValues["area"] = GetControllerArea(controllerType);
            routeValues["controller"] = controllerTypeName.EndsWith("Controller") ? controllerTypeName.Remove(controllerTypeName.Length - 10, 10) : controllerTypeName;
            routeValues["action"] = methodCall.Method.Name;

            if (methodCall.Arguments.Count != 0)
            {
                var parameterIndex = 0;
                var parameters = methodCall.Method.GetParameters();
                foreach (var arg in methodCall.EvalArguments())
                {
                    routeValues.Add(parameters[parameterIndex].Name, arg);
                    parameterIndex++;
                }
            }

            return routeValues;
        }

        public string GetControllerArea(ControllerDescriptor controllerDescriptor)
        {
            if (controllerDescriptor == null) throw new ArgumentNullException("controllerDescriptor");

            return (from route in RouteTable.Routes.OfType<Route>()
                    where route.DataTokens != null
                    let namespaces = (string[])route.DataTokens["Namespaces"]
                    where namespaces != null
                    from pattern in namespaces
                    where !string.IsNullOrEmpty(pattern) && MatchNamespace(pattern, controllerDescriptor.ControllerType.Namespace) 
                    select (string)route.DataTokens["area"]).FirstOrDefault();
        }

        public ModelMetadata GetMetadataForType(Type modelType)
        {
            return ModelMetadataProviders.Current.GetMetadataForType(() => null, modelType);
        }

        public string GetShortModelName(Type type)
        {
            var modelMetadata = GetMetadataForType(type);
            return modelMetadata.ShortDisplayName ?? modelMetadata.ModelType.Name;
        }

        #endregion

        #region private IServiceProvider ServiceProvider

        private readonly IServiceProvider m_ServiceProvider;

        private IServiceProvider ServiceProvider
        {
            get
            {
                return m_ServiceProvider;
            }
        }

        #endregion

        private string GetControllerArea(Type controllerType)
        {
            if (controllerType == null) throw new ArgumentNullException("controllerDescriptor");

            return (from route in RouteTable.Routes.OfType<Route>()
                    where route.DataTokens != null
                    let namespaces = (string[])route.DataTokens["Namespaces"]
                    where namespaces != null
                    from pattern in namespaces
                    where !string.IsNullOrEmpty(pattern) && MatchNamespace(pattern, controllerType.Namespace)
                    select (string)route.DataTokens["area"]).FirstOrDefault("");
        }

        private bool MatchNamespace(string pattern, string ns)
        {
            var nsComponents = ns.Split('.');
            var patternComponents = pattern.Split('.');

            for (var i = 0; i < patternComponents.Length; i++)
            {
                if (patternComponents[i] == "*")
                    continue;
                else if (patternComponents[i] != nsComponents[i])
                    return false;
            }
            return true;
        }
    }
}